package com.lenin.omena.server;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.jdo.annotations.Persistent;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpSession;

import org.springframework.transaction.annotation.Transactional;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.lenin.omena.shared.ActivateResult;
import com.lenin.omena.shared.AddProduceResult;
import com.lenin.omena.shared.AppleMarketService;
import com.lenin.omena.shared.DeleteProduceResult;
import com.lenin.omena.shared.DeleteUserResult;
import com.lenin.omena.shared.LoginResult;
import com.lenin.omena.shared.SaveProduceResult;
import com.lenin.omena.shared.SaveUserInfoResult;
import com.lenin.omena.shared.SaveUserResult;
import com.lenin.omena.shared.domain.Address;
import com.lenin.omena.shared.domain.Produce;
import com.lenin.omena.shared.domain.Site;
import com.lenin.omena.shared.domain.User;
import com.lenin.omena.shared.domain.UserInfo;

/**
 * The server side implementation of the RPC service.
 */
//@Service("greetingService")
public class AppleMarketServiceImpl extends RemoteServiceServlet implements
		AppleMarketService {
	
	private Set serializedPool;
	
	private UserDAO userDAO = new UserDAO();
	
	private SiteDAO siteDAO = new SiteDAO();
	
	private ProduceDAO produceDAO = new ProduceDAO();
	
	private UserInfoDAO userInfoDAO = new UserInfoDAO();
	
	private AddressDAO addressDAO = new AddressDAO();
	
	
	public ActivateResult activateUser(String userKey) {
		
		User user = userDAO.findByKey(userKey);
		
		ActivateResult result = null;
		
		if(user != null) {
			
			Integer userStatus = user.getStatus();
			
			if(userStatus == null) {
			
				user = userDAO.setStatus(user, 1);
				result = new ActivateResult(detachUser(user), 0);
			
			} else {
				
				result = new ActivateResult(detachUser(user), 1);
				
			}
			
			
			
		} else {
			
			result = new ActivateResult(null, -1);
			
		}
		
		return result;
		
	}
	

	public LoginResult logIn(String userName, String password) {
		
		HttpSession session = getThreadLocalRequest().getSession();
		
		String userKey = (String)session.getAttribute("auth");
		
		User detachedUser = null;
		Integer status = -1;
		
		if(userKey != null) {
			User user = userDAO.findByKey(userKey);
			detachedUser = detachUser(user);
			Address address = addressDAO.findByKey(detachedUser.getUserInfo().getSiteAddressKey());
			detachedUser.getUserInfo().setSiteAddress(address);
			return new LoginResult(detachedUser, 0);
		}
		
		User user = userDAO.getUserByUserName(userName);
		
		if(user != null) {
			
			Integer userStatus = user.getStatus();
			UserInfo userInfo = user.getUserInfo();
			
			if(userStatus == null) {
				status = 3;
			} else if(userInfo.getPassword().equals(password)) {
				session.setAttribute("auth", user.getKey());
				detachedUser = detachUser(user);
				status = 0;
			} else {
				status = 1;
			}
			
		} else {
			
			if(userName.length() == 0 && password.length() == 0)
				status = 4;
			else
				status = 2;
		
		}
		
		if(detachedUser != null) {
			Address address = addressDAO.findByKey(detachedUser.getUserInfo().getSiteAddressKey());
			detachedUser.getUserInfo().setSiteAddress(address);
		}
		
		return new LoginResult(detachedUser, status);
		
	}
	
	
	public LoginResult logOut() {
		
		HttpSession session = getThreadLocalRequest().getSession();
		session.removeAttribute("auth");
		session.invalidate();
		
		return new LoginResult(null, 0);
		
	}
	
	
	@Transactional
	public DeleteUserResult deleteUser(String userKey) {
		
		DeleteUserResult result = null;
		int resultCode = 0;
		
		User user = userDAO.findByKey(userKey);
		
		User u = null;
		
		if(user != null) {
			
			u = detachUser(user);
			
			user = userDAO.remove(user);
			
			if(user == null) {
				resultCode = 2;
			}
			
		} else {
			
			resultCode = 1;
		}
		
		return new DeleteUserResult(u, resultCode);
		
	}
	
	
	@Transactional
	public SaveUserResult saveUser(User user) {
		
		SaveUserResult result = null;
		int resultCode = 0;
		
		User u = null;
		
		if(user.getKey() != null) {
			
			u = userDAO.findByKey(user.getKey());
			if(u != null)
				resultCode = 1;
			
		} else {
			
			u = userDAO.getUserByUserName(user.getUserInfo().getUserName());
			if(u != null) {
				resultCode = 2;
				u = null;
			}
			
		}
		
		if(resultCode == 0 || resultCode == 1) {
			
			u = userDAO.persist(user);
			UserInfo userInfo = u.getUserInfo();
			userInfoDAO.saveUserInfo(userInfo, user.getUserInfo());
			u = detachUser(u);
			
			if(resultCode == 0) {
				sendConfirmationEmail(user.getKey());
			}
			
		}
		
		
		
		result = new SaveUserResult(u, resultCode);
		
		return result;
		
	}
	
	
	@Transactional
	public SaveUserInfoResult saveUserInfo(String userKey, UserInfo detachedUserInfo) {
		
		User user = userDAO.findByKey(userKey);
		
		Site site = user.getSite();
		Address address = siteDAO.saveAddress(site, detachedUserInfo.getSiteAddress());
		detachedUserInfo.getSiteAddress().setKey(address.getKey());
		
		UserInfo userInfo = user.getUserInfo();
		UserInfo info = userInfoDAO.saveUserInfo(userInfo, detachedUserInfo);
		
		info = userInfoDAO.detachCopy(info);
		info.setSiteAddress(address);
		
		SaveUserInfoResult result = new SaveUserInfoResult(info, 0);
		
		return result;
		
	}
	
	
	
	public List<User> getAllUsers() {
		
		List<User> users = userDAO.findAll();
		
		List<User> detachedUsers = detachUsers(users);
		
		
		return detachedUsers;
		
	}
	
	
	@Transactional
	public DeleteProduceResult deleteProduce(String userKey, String produceKey) {
		
		User user = userDAO.findByKey(userKey);
		Produce produce = produceDAO.findByKey(produceKey);
		
		userDAO.deleteProduce(user, produce);
		
		List<Produce> p = user.getSite().getProduce();
		
		p = produceDAO.detachCopy(p);
		
		DeleteProduceResult result = new DeleteProduceResult(p, 0);
		
		return result;
		
	}
	
	
	@Transactional
	public AddProduceResult addProduce(String userKey, Produce produce) {
		
		User user = userDAO.findByKey(userKey);
		
		userDAO.addProduce(user, produce);
		
		List<Produce> p = user.getSite().getProduce();
		
		p = produceDAO.detachCopy(p);
		
		AddProduceResult result = new AddProduceResult(p, 0);
		
		return result;
		
	}
	
	
	@Transactional
	public SaveProduceResult saveProduce(Produce detachedProduce) {
		
		String produceKey = detachedProduce.getKey();
		Produce produce = produceDAO.findByKey(produceKey);
		
		Produce p = produceDAO.saveProduce(produce, detachedProduce);
		p = produceDAO.detachCopy(p);
		
		SaveProduceResult result = new SaveProduceResult(p, false);
		
		return result;
		
	}
	
	
	@Transactional
	public Integer test(User user, Produce produce) {
		
		userDAO.persist(user);
		
		User u = userDAO.findByKey(user.getKey());
		
		return 0;
		
	}
	
	
	private User detachUser(User user) {
		
		User detachedUser = userDAO.detachCopy(user);
		
		Site detachedSite = siteDAO.detachCopy(user.getSite());
		List<Produce> detachedProduce = produceDAO.detachCopy(user.getSite().getProduce());
		
		detachedSite.setProduce(detachedProduce);
		detachedUser.setSite(detachedSite);
		
		return detachedUser;
		
	}
	
	
	private List<User> detachUsers(List<User> users) {
		
		ArrayList<User> detachedUsers = new ArrayList<User>();
		
		for(User user : users) {
			detachedUsers.add(detachUser(user));
		}
		
		return detachedUsers;
		
	}
	
	
	private boolean isCustomClass(Class clazz) {

		return (!clazz.equals(String.class) && !clazz.equals(Long.class)
				&& !clazz.equals(Integer.class) && !clazz.equals(Boolean.class) && !clazz
					.equals(Double.class));

	}
	

	protected List serializeList(List list) {

		ArrayList serializedList = new ArrayList();

		for (Object result : list) {
			try {
				serializeWithDependencies(result);
				serializedList.add(result);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return serializedList;

	}

	protected void serializeWithDependencies(Object entity) {

		try {
			serializedPool = new HashSet();
			serialize(entity);
			serializedPool = null;
		} catch(Exception e) {
			e.printStackTrace();
		}

	}

	protected void serialize(Object entity) throws Exception {

		if (entity == null || !isCustomClass(entity.getClass()))
			return;

		if (serializedPool != null) {
			if (serializedPool.contains(entity))
				return;
			else
				serializedPool.add(entity);
		}

		Field[] fields = entity.getClass().getDeclaredFields();
		Method[] methods = entity.getClass().getDeclaredMethods();

		for (Field field : fields) {

			String fieldName = field.getName();
			String methodSuffix = fieldName.substring(0, 1).toUpperCase()
					+ fieldName.substring(1);

			String getterName = "get" + methodSuffix;
			String setterName = "set" + methodSuffix;

			Persistent p = field.getAnnotation(Persistent.class);

			Class clazz = field.getType();

			if (p != null && isCustomClass(clazz) && serializedPool != null) {

				Method getterMethod = entity.getClass().getDeclaredMethod(
						getterName);
				Method setterMethod = entity.getClass().getDeclaredMethod(
						setterName, clazz);

				boolean isList = clazz.equals(List.class);

				if (isList) {

					boolean customGeneric = false;
					Class classParam = null;

					Type genType = getterMethod.getGenericReturnType();
					if (genType instanceof ParameterizedType) {
						ParameterizedType type = (ParameterizedType) genType;
						Type[] typeArguments = type.getActualTypeArguments();
						if (typeArguments.length > 0) {
							if (isCustomClass((Class) typeArguments[0])) {
								customGeneric = true;
								classParam = (Class) typeArguments[0];
							}
						}
					}

					List list = (List) getterMethod.invoke(entity);
					ArrayList arrList = new ArrayList();
					arrList.add("diff");

					setterMethod.invoke(entity, arrList);
					arrList.remove(0);

					for (Object o : list) {

						if (customGeneric)
							serialize(classParam.cast(o));
						arrList.add(o);

					}

				} else {

					Object o = getterMethod.invoke(entity);
					serialize(clazz.cast(o));
					setterMethod.invoke(entity, o);

				}

			}

		}

	}
	
	
	private void sendConfirmationEmail(String userKey) {
		
		System.out.println("sending key: "+userKey);
		
		Properties props = new Properties();
        Session session = Session.getInstance(props, null);
        
        User user = userDAO.findByKey(userKey);
        UserInfo userInfo = user.getUserInfo();
        String emailAddress = userInfo.getEmail();
        
        String msgBody = 
        		"Kiitos rekisteröitymisestä Omenapörssiin! \r\n\r\n" +
        		"Klikkaa seuraavaa linkkiä aktivoidaksesi tunnuksesi: \r\n\r\n" +
        		"http://omenaporssi.appspot.com/?key="+userKey +
        		"\r\n\r\n\r\n" +
        		"Kiitos, \r\n" +
        		"Omenapörssi";

        try {
        	
            Message msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress("admin@omenaporssi.appspotmail.com", "Omenapörssi Admin"));
            msg.addRecipient(Message.RecipientType.TO,
                             new InternetAddress(emailAddress, userInfo.getUserName()));
            msg.setSubject("Omenapörssi: tunnuksen aktivointi");
            msg.setText(msgBody);
            Transport.send(msg);

        } catch (Exception e) {
        	e.printStackTrace();
        }
        
		
	}
	
	
	
}
